#include "file_manager.h"
#include "escal.h"
using namespace Escal;

FileManager::FileManager(QString folderName, QString fileName, 
	QObject *parent)
{
	newFile = true;
	fileSize = 0;
	finishedSize = 0;
	acceptRange = false;
	pieceNum = 0;
	error = false;
	quit = false;
	setPaths(folderName, fileName);
};

FileManager::~FileManager()
{
	qDebug() << "FileManager::~FileManager()";
	quit = true;
	cond.wakeOne();
	wait();
	file.close();
	digestFile.close();
	if(piecesDone.count(false)==0) digestFile.remove();
}

bool FileManager::setPaths(const QString &folderName, const QString &fileName){
	this->folderName = folderName ;
	this->fileName = fileName ; 
	this->digestFileName = fileName + DigestFileSuffix;
	dir.setPath(this->folderName);
	file.setFileName(dir.filePath(this->fileName));
	digestFile.setFileName(dir.filePath(this->digestFileName));
	return true;
}

void FileManager::checkDigest(){
	QMutexLocker locker(&mutex);
	if(!dir.exists()){
		if(!dir.mkdir(folderName)){
			exit(1);
		}
	}
	if(!file.exists()){
		if(!file.open(QIODevice::ReadWrite)){
			exit(1);
		}
		fileSize = -1;
	}
	else {
		file.open(QIODevice::ReadOnly);
		fileSize = file.size();
		qDebug() << fileSize;

	}
	file.close();
	digestFile.setFileName(dir.filePath(digestFileName));
	if(!digestFile.exists()){
		if(!digestFile.open(QIODevice::ReadWrite)){
			exit(1);
		}
		digestFile.close();
		digestFile.remove();
	}
	digestFile.close();
	if(loadDigest()){
		newFile=false;
		emit fileIsInitialized();
	}
}

bool FileManager::initNewFile(const qint64 &fileSize ,bool acceptRange){
	QMutexLocker locker(&mutex);
	if(! newFile ) return true;
	this->fileSize = fileSize;
	this->acceptRange = acceptRange;
	pieceNum = fileSize / ( PieceSize ) + 1;
	piecesWorking.resize(pieceNum);
	piecesDone.resize(pieceNum);
	file.resize(fileSize);
	newFile = false;
	emit fileIsInitialized();
	return true;
}

int FileManager::read(int pieceIndex, qint64 offset, int length){
	return 0;

}

bool FileManager::write(int &pieceIndex, qint64 &offset, const QByteArray &data){
	/*
	 * return true means your work is done, stop
	 * return false means it is not done, keep working
	 */
	WriteRequest newRequest;
	newRequest.pieceIndex = pieceIndex;
	newRequest.offset = offset;
	newRequest.data = data;
	QMutexLocker locker(&mutex);
	writeRequests << newRequest;
	offset += data.size();
	if(offset + pieceIndex * PieceSize > fileSize){
		qDebug() << "offset + pieceIndex * PieceSize > getFileSize()\n" << endl;;
	qDebug() << "offset + pieceIndex * PieceSize: "
			 << offset + pieceIndex * PieceSize
		 << "\n getFileSize(): " << getFileSize() << endl;
	error = true;
	quit = true;
	cond.wakeOne();
	return false;
	}
	else if(offset + pieceIndex * PieceSize == fileSize){
	piecesWorking[pieceIndex]=false;
	int i;
	for(i=pieceIndex;i<pieceNum-1;i++){
		if(piecesDone[i] || piecesWorking[i]){
			finishedSize -= PieceSize;
		}
			piecesDone[i]=true;
	}
		if(piecesDone[i] || piecesWorking[i]){ 
		finishedSize -= (fileSize - PieceSize * (pieceNum - 1));
	}
	piecesDone[i]=true;
	finishedSize += (fileSize - (pieceIndex * PieceSize));
	progressForwarded = true;
	cond.wakeOne();
	return true;
	}
	else if(pieceIndex < pieceNum -1 && 
		offset >= PieceSize)
	{
	piecesWorking[pieceIndex]=false;
		while(offset >= PieceSize){
		if(!piecesWorking[pieceIndex] && !piecesDone[pieceIndex]){
			finishedSize += PieceSize;
		}
		progressForwarded = true;
		piecesDone[pieceIndex]=true;
		offset -= PieceSize;
		pieceIndex ++;
	}
	cond.wakeOne();

	if(piecesWorking[pieceIndex]||piecesDone[pieceIndex]){
		return true;
	}
	else{
		piecesWorking[pieceIndex]=true;
		return false;
	} 
	}
	else {
		cond.wakeOne();

	return false;
	}
}

QByteArray FileManager::readBlock(int pieceIndex, qint64 offset, int length){
	return QByteArray();
}

bool FileManager::writeBlock(int pieceIndex, qint64 offset, const QByteArray &data){
	if(!file.isOpen()){
		if(!file.open(QIODevice::ReadWrite)){
		exit(1);
	}
	}
	qint64 totalOffset = pieceIndex * PieceSize + offset;
	file.seek(totalOffset);
	if(file.write(data) < 0){
		exit(1);
	}
	return true;
}

int FileManager::getPieceNum(){
	return pieceNum;
}

int FileManager::getUndonePieceNum(){
	return piecesDone.count(false);
}

bool FileManager::isAcceptRange(){
	return acceptRange;
}

void FileManager::setAcceptRange(){
	acceptRange = true;
}

// search for the best piece to give,
// mask it as working then return the pieceIndex
int FileManager::getPiece(int formerPieceIndex){
	QMutexLocker locker(&mutex);
	if(formerPieceIndex >= 0){
		piecesWorking[formerPieceIndex]=false;
	// the next piece is good for a link
		if(formerPieceIndex < pieceNum -1 && 
			!piecesWorking[formerPieceIndex+1] && 
		!piecesDone[formerPieceIndex+1])
		{
			piecesWorking[formerPieceIndex+1]=true;
			return formerPieceIndex+1;
		}
	}
	// now we should search for the longest piece
	int pIndex=0, pLength=0, i;
	int pShuttle=0, ShuttleLength=0;


	while (( piecesWorking[pShuttle] || piecesDone[pShuttle] )&&
		pieceNum > pShuttle )
		pShuttle++;
	if(pShuttle == pieceNum)
		return -1;

	ShuttleLength=0;
	for(i=pShuttle ;i<pieceNum && !piecesDone[i] 
		&& !piecesWorking[i]; i++)
	{
	ShuttleLength++;
	}
	while(pShuttle < pieceNum){
		if(pShuttle>=1 && piecesWorking[pShuttle-1]){
			if(ShuttleLength /2 +1 > pLength){
			pLength = ShuttleLength /2 + 1;
		pIndex = pShuttle + ShuttleLength /2;
		}
	}
	else{
		if(ShuttleLength > pLength){
			pLength = ShuttleLength;
		pIndex = pShuttle;
		}
	}
	pShuttle += (ShuttleLength+1);

	ShuttleLength=0;
   	while(piecesWorking[pShuttle]||piecesDone[pShuttle]) pShuttle++;
	for(i=pShuttle ;i<pieceNum && piecesDone[i]==false 
			&& piecesWorking[i]==false; i++)
		{
		ShuttleLength++;
		}
	}
	piecesWorking[pIndex]=true;
	return pIndex;
}

void FileManager::cleanUp(){
	file.close();
	digestFile.close();
}

void FileManager::releasePiece(int pieceIndex){
	QMutexLocker locker(&mutex);
	piecesWorking[pieceIndex] = false;
}


/* void FileManager::setPieceDone(int pieceIndex){
	QMutexLocker locker(&mutex);
	piecesDone[pieceIndex] = true;
	cond.wakeOne();
} */

void FileManager::run(){
	// a uninitialized file. wait for it to be initialized.
	quit=false;
	if(newFile) { 
		mutex.lock();
	cond.wait(&mutex);
	mutex.unlock();
	}
	do {
		// Go to sleep if there's nothing to do.
	mutex.lock();
	if(!quit && readRequests.isEmpty() && writeRequests.isEmpty() ){
		cond.wait(&mutex);
	} 
	mutex.unlock();
	// Read pending read requests
	mutex.lock();
		QList<ReadRequest> newReadRequests = readRequests;
		readRequests.clear();
		mutex.unlock();
		while (!newReadRequests.isEmpty()) {
			ReadRequest request = newReadRequests.takeFirst();
		QByteArray block = readBlock(request.pieceIndex, request.offset, request.length);
			//emit dataRead(request.id, request.pieceIndex, request.offset, block);
		}

	// Write pending write requests
		mutex.lock();
		QList<WriteRequest> newWriteRequests = writeRequests;
		writeRequests.clear();
	while (!quit && !newWriteRequests.isEmpty()) {
			WriteRequest request = newWriteRequests.takeFirst();
			writeBlock(request.pieceIndex, request.offset, request.data);
		}
	if(progressForwarded){
		emit progressForward();
			progressForwarded = false;
		file.flush();
		syncDigest();
	}
	if(getUndonePieceNum()==0){
		mutex.unlock();
		break;
	}
		mutex.unlock();
	} while (!quit );
};

void FileManager::syncDigest(){
	if(!digestFile.isOpen()){
		if(!digestFile.open(QIODevice::Truncate|QIODevice::WriteOnly)){
		exit(1);
		}
	}
	QDataStream digestData(&digestFile);
	digestData << acceptRange
		   << fileSize 
		   << finishedSize
		   << qint32(pieceNum) 
		   << dateString
		   << piecesDone ;
	digestFile.close();	   
}

bool FileManager::loadDigest(){
	if(!digestFile.isOpen()){
		if(!digestFile.open(QIODevice::ReadOnly)){
		exit(1);
		}
	}
	QDataStream digestData(&digestFile);
	digestData >> acceptRange
		   >> fileSize 
		   >> finishedSize
		   >> pieceNum 
		   >> dateString
		   >> piecesDone ;
	digestFile.close();
	if(digestData.status() == QDataStream::ReadPastEnd){
		fileSize = -1;
		return false;
	}
	piecesWorking.resize(pieceNum);
	return true;
}
